1 <?php
2     $admin_dir = dirname(__FILE__);
3     require(
"{$admin_dir}/incCommon.php");
4
5     $GLOBALS[
'DEBUG_MODE'] = false;
6     $csv =
new Backup($_REQUEST);
7
8     
class Backup{
9         
private $curr_dir,
10                 $curr_page,
11                 $lang,
/* translation text */
12                 $request,
/* assoc array that stores $_REQUEST */
13                 $error_back_link,
14                 $backup_log,
15                 $initial_ts;
/* initial timestamp */
16
17         
public function __construct($request = array()){
18             
global $Translation;
19
20             $
this->curr_dir = dirname(__FILE__);
21             $
this->curr_page = basename(__FILE__);
22             $
this->initial_ts = microtime(true);
23             $
this->lang = $Translation;
24
25             
/* back link to use in errors */
26             $
this->error_back_link = '' .
27                 
'<div class="text-center vspacer-lg"><a href="' . $this->curr_page . '" class="btn btn-danger btn-lg">' .
28                     
'<i class="glyphicon glyphicon-chevron-left"></i> ' .
29                     $
this->lang['back and retry'] .
30                 
'</a></div>';
31
32             
/* create backup folder if needed */
33             
if(!$this->create_backup_folder() && is_xhr()){
34                 @header(
"{$_SERVER['SERVER_PROTOCOL']} 500 Internal Server Error");
35                 
return;
36             }
37
38             
/* process request to retrieve $this->request, and then execute the requested action */
39             $
this->process_request($request);
40             $
out = call_user_func_array(array($this, $this->request['action']), array());
41             
if($out === true || $out === false){
42                 echo $
this->backup_log;
43                 
if(!$out) @header("{$_SERVER['SERVER_PROTOCOL']} 500 Internal Server Error");
44                 
return;
45             }
46             echo $
out;
47         }
48
49         
protected function debug($msg, $html = true){
50             
if($GLOBALS['DEBUG_MODE'] && $html) return "<pre>DEBUG: {$msg}</pre>";
51             
if($GLOBALS['DEBUG_MODE']) return " [DEBUG: {$msg}] ";
52             
return '';
53         }
54
55         
protected function elapsed(){
56             
return number_format(microtime(true) - $this->initial_ts, 3);
57         }
58
59         
protected function process_request($request){
60             
/* action must be a valid controller, else set to default (main) */
61             $controller = isset($request[
'action']) ? $request['action'] : false;
62             
if(!in_array($controller, $this->controllers())) $request['action'] = 'main';
63
64             $
this->request = $request;
65         }
66
67         
/**
68          * discover the
public functions in this class that can act as controllers
69          *
70          * @
return array of public function names
71          */

72         
protected function controllers(){
73             $csv =
new ReflectionClass($this);
74             $methods = $csv->getMethods(ReflectionMethod::IS_PUBLIC);
75
76             $controllers = array();
77             
foreach($methods as $mthd){
78                 $controllers[] = $mthd->name;
79             }
80
81             
return $controllers;
82         }
83
84         
protected function request_or($var, $default){
85             
return (isset($this->request[$var]) ? $this->request[$var] : $default);
86         }
87
88         
protected function header(){
89             $Translation = $
this->lang;
90             ob_start();
91             $GLOBALS[
'page_title'] = $Translation['database backups'];
92             include(
"{$this->curr_dir}/incHeader.php");
93             $
out = ob_get_contents();
94             ob_end_clean();
95
96             
return $out;
97         }
98
99         
protected function footer(){
100             $Translation = $
this->lang;
101             ob_start();
102             include(
"{$this->curr_dir}/incFooter.php");
103             $
out = ob_get_contents();
104             ob_end_clean();
105
106             
return $out;
107         }
108
109         
/**
110          * @brief UTF8-encodes a
string/array
111          * @see https://stackoverflow.com/a/
26760943/1945185
112          *
113          * @param [
in] $mixed string or array of strings to be UTF8-encoded
114          * @
return UTF8-encoded array/string
115          */

116         
protected function utf8ize($mixed) {
117             
if(!is_array($mixed)) return to_utf8($mixed);
118
119             
foreach($mixed as $key => $value){
120                 $mixed[$key] = $
this->utf8ize($value);
121             }
122             
return $mixed;
123         }
124
125         
/**
126          * @brief Retrieves and validates user-specified md5_hash and checks
if it matches a backup file
127          *
128          * @
return False on error, backup file full path on success.
129          */

130         
protected function get_specified_backup_file(){
131             $md5_hash = $
this->request['md5_hash'];
132             
if(!preg_match('/^[a-f0-9]{32}$/i', $md5_hash)) return false;
133
134             $bfile =
"{$this->curr_dir}/backups/{$md5_hash}.sql";
135             
if(!is_file($bfile)) return false;
136
137             
return $bfile;
138         }
139
140         
/**
141          * function to show main page
142          */

143         
public function main(){
144             ob_start();
145
146             echo $
this->header();
147
148             $can_backup = $
this->create_backup_folder();
149
150             ?>
151             <div
class="page-header"><h1><?php echo $this->lang['database backups']; ?></h1></div>
152
153             <div id=
"in-page-notifications"></div>
154             <script>
155                 
/* move notifications below page title */
156                 $j(function(){
157                     $j(
'.notifcation-placeholder').appendTo('#in-page-notifications');
158                 })
159             </script>
160
161             <?php
162                 echo Notification::show(array(
163                     
'message' => '<i class="glyphicon glyphicon-info-sign"></i> ' . $this->lang['about backups'],
164                     
'class' => 'info',
165                     
'dismiss_days' => 30,
166                     
'id' => 'info-about-backups'
167                 ));
168
169                 
if(!$can_backup){
170                     echo Notification::show(array(
171                         
'message' => $this->lang['cant create backup folder'],
172                         
'class' => 'danger',
173                         
'dismiss_seconds' => 900
174                     ));
175                 }
176             ?>
177
178             <?php
if($can_backup){ ?>
179                 <button type=
"button" class="vspacer-lg btn btn-primary btn-lg" id="create-backup"><i class="glyphicon glyphicon-plus"></i> <?php echo $this->lang['create backup file']; ?></button>
180                 <pre id=
"backup-log" class="hidden"></pre>
181
182                 <h2><?php echo $
this->lang['available backups']; ?></h2>
183                 <div id=
"backup-files-list"></div>
184
185                 <style>
186                     .backup-file{
187                         border-bottom: solid 1px #aaa;
188                         padding: .75em;
189                         margin:
0;
190                     }
191                 </style>
192                 <script>
193                     $j(function(){
194                         
/* language strings */
195                         
var create_backup = '<?php echo html_attr($this->lang['create backup file']); ?>';
196                         
var please_wait = '<?php echo html_attr($this->lang['please wait']); ?>';
197                         
var finished = '<?php echo html_attr($this->lang['done!']); ?>';
198                         
var error = '<?php echo html_attr($this->lang['error']); ?>';
199                         
var no_matches = '<?php echo html_attr($this->lang['no backups found']); ?>';
200                         
var restore_backup = '<?php echo html_attr($this->lang['restore backup']); ?>';
201                         
var delete_backup = '<?php echo html_attr($this->lang['delete backup']); ?>';
202                         
var confirm_backup = '<?php echo html_attr($this->lang['confirm backup']); ?>';
203                         
var confirm_restore = '<?php echo html_attr($this->lang['confirm restore']); ?>';
204                         
var confirm_delete = '<?php echo html_attr($this->lang['confirm delete backup']); ?>';
205                         
var backup_restored = '<?php echo html_attr($this->lang['backup restored']); ?>';
206                         
var backup_deleted = '<?php echo html_attr($this->lang['backup deleted']); ?>';
207                         
var delete_error = '<?php echo html_attr($this->lang['backup delete error']); ?>';
208                         
var restore_error = '<?php echo html_attr($this->lang['restore error']); ?>';
209
210                         
var page = '<?php echo $this->curr_page; ?>';
211                         
var backup_files_list = $j('#backup-files-list');
212
213                         
var clear_list = function(){
214                             backup_files_list.html(
'<div class="alert alert-warning">' + no_matches + '</div>');
215                         }
216
217                         
var display_backups = function(){
218                             $j.ajax({
219                                 url: page,
220                                 data: { action:
'get_backup_files' },
221                                 success: function(resp){
222                                     
try{
223                                         
var list = JSON.parse(resp);
224                                         
if(list.constructor !== Array) throw 'not a list of files';
225
226                                         backup_files_list.html(
'');
227                                         
for(var i = 0; i < list.length; i++){
228                                             backup_files_list.append(
229                                                 
'<h4 class="hspacer-lg backup-file">' +
230                                                     
'<div class="btn-group hspacer-lg">' +
231                                                         
'<button type="button" class="btn btn-default restore" data-md5_hash="' + list[i].md5_hash + '"><i class="glyphicon glyphicon-download-alt"></i> ' + restore_backup + '</button>' +
232                                                         
'<button type="button" class="btn btn-default delete" data-md5_hash="' + list[i].md5_hash + '"><i class="glyphicon glyphicon-trash"></i> ' + delete_backup + '</button>' +
233                                                     
'</div>' +
234                                                     list[i].datetime +
235                                                     
' (' + list[i].size + ' KB) ' +
236                                                 
'</h4>'
237                                             );
238                                         }
239                                     }
catch(e){
240                                         clear_list();
241                                     }
242                                 },
243                                 error: clear_list
244                             });
245                         };
246
247                         backup_files_list
248                             .
on('click', '.restore', function(){
249                                 
/* confirm restore */
250                                 
if(!confirm(confirm_restore)) return;
251
252                                 $j.ajax({
253                                     url: page,
254                                     data: { action:
'restore', md5_hash: $j(this).data('md5_hash') },
255                                     success: function(){
256                                         show_notification({
257                                             message: backup_restored,
258                                             
class: 'success',
259                                             dismiss_seconds:
30
260                                         });
261                                     },
262                                     error: function(){
263                                         show_notification({
264                                             message: restore_error,
265                                             
class: 'danger',
266                                             dismiss_seconds:
30
267                                         });
268                                     },
269                                     complete: display_backups
270                                 });
271                             })
272                             .
on('click', '.delete', function(){
273                                 
/* confirm delete backup */
274                                 
if(!confirm(confirm_delete)) return;
275
276                                 $j.ajax({
277                                     url: page,
278                                     data: { action:
'delete', md5_hash: $j(this).data('md5_hash') },
279                                     success: function(){
280                                         show_notification({
281                                             message: backup_deleted,
282                                             
class: 'success',
283                                             dismiss_seconds:
30
284                                         });
285                                     },
286                                     error: function(){
287                                         show_notification({
288                                             message: delete_error,
289                                             
class: 'danger',
290                                             dismiss_seconds:
30
291                                         });
292                                     },
293                                     complete: display_backups
294                                 });
295                             })
296                             .
on('mouseover', 'h4', function(){
297                                 $j(
this).addClass('bg-warning');
298                             })
299                             .
on('mouseout', 'h4', function(){
300                                 $j(
this).removeClass('bg-warning');
301                             });
302
303                         $j(
'#create-backup').click(function(){
304                             
if(!confirm(confirm_backup)) return;
305
306                             $j(
'#backup-log').html('').addClass('hidden');
307
308                             
var btn = $j(this);
309                             btn.addClass(
'btn-warning').prop('disabled', true).html('<i class="glyphicon glyphicon-hourglass"></i> ' + please_wait);
310                             $j.ajax({
311                                 url: page,
312                                 data: { action:
'create_backup' },
313                                 success: function(){
314                                     btn.removeClass(
'btn-warning btn-primary').addClass('btn-success').html('<i class="glyphicon glyphicon-ok"></i> ' + finished);
315                                 },
316                                 error: function(){
317                                     btn.removeClass(
'btn-warning btn-primary').addClass('btn-danger').html('<i class="glyphicon glyphicon-remove"></i> ' + error);
318                                 },
319                                 complete: function(jx){
320                                     
if(jx.responseText.length > 0) $j('#backup-log').html(jx.responseText).removeClass('hidden');
321                                     display_backups();
322                                     setTimeout(function(){
323                                         btn.removeClass(
'btn-danger btn-warning').addClass('btn-primary').prop('disabled', false).html('<i class="glyphicon glyphicon-plus"></i> ' + create_backup);
324                                     },
10000);
325                                 }
326                             });
327                         });
328
329                         
/* keep removing dismissed notifications from DOM */
330                         setInterval(function(){
331                             $j(
'.notifcation-placeholder .invisible').remove();
332                         },
1000);
333
334                         display_backups();
335                     })
336                 </script>
337             <?php } ?>
338
339             <?php
340             echo $
this->footer();
341
342             $html = ob_get_clean();
343
344             
return $html;
345         }
346
347         
/**
348          * @brief Retrieve a list of available backup files, with dates and times
349          *
350          * @
return Array of backup files [[md5_hash => '', datetime => 'y-m-d H:i:s', size => '659888'], ..]
351          *
352          * @details Backup files are those found
in the folder 'backups' named as an md5 hash with .sql extension.
353          */

354         
public function get_backup_files(){
355             $bdir = $
this->curr_dir . '/backups';
356             $d = dir($bdir);
357             
if(!$d) return false;
358             $admin_cfg = config(
'adminConfig');
359             $dtf = $admin_cfg[
'PHPDateTimeFormat'];
360
361             $list = array();
362
363             
while(false !== ($entry = $d->read())){
364                 
if(!preg_match('/^[a-f0-9]{32}\.sql$/i', $entry)) continue;
365                 $fts = @filemtime(
"{$bdir}/{$entry}");
366                 $list[$fts] = array(
367                     
'md5_hash' => substr($entry, 0, 32),
368                     
'datetime' => date($dtf, $fts),
369                     
'size' => number_format(@filesize("{$bdir}/{$entry}") / 1024)
370                 );
371             }
372
373             $d->close();
374             
if(!count($list)) return false;
375
376             krsort($list);
377             
return json_encode(array_values($list));
378         }
379
380         
/**
381          * @brief create a
new backup file
382          *
383          * @
return Boolean indicating success or failure
384          *
385          * @details Uses mysqldump (
if available) to create a new backup file
386          */

387         
public function create_backup(){
388             $config = array(
'dbServer' => '', 'dbUsername' => '', 'dbPassword' => '', 'dbDatabase' => '');
389             
foreach($config as $k => $v) $config[$k] = escapeshellarg(config($k));
390
391             $dump_file = $
this->curr_dir . '/backups/' . md5(microtime()) . '.sql';
392             $
out = array(); $ret = 0;
393             maintenance_mode(
true);
394             $pass_param = ($config[
'dbPassword'] ? "-p{$config['dbPassword']}" : '');
395             @exec(
"(mysqldump -u{$config['dbUsername']} {$pass_param} -h{$config['dbServer']} {$config['dbDatabase']} > {$dump_file}) 2>&1", $out, $ret);
396             $
this->backup_log = implode("\n", $out);
397             maintenance_mode(
false);
398
399             
if($ret) return false;
400
401             
return true;
402         }
403
404         
/**
405          * @brief Restores a given backup file
406          *
407          * @
return Boolean indicating success or failure
408          *
409          * @details Overwrites existing data
in the database, including users and groups.
410          */

411         
public function restore(){
412             $bfile = $
this->get_specified_backup_file();
413             
if(!$bfile) return false;
414
415             $config = array(
'dbServer' => '', 'dbUsername' => '', 'dbPassword' => '', 'dbDatabase' => '');
416             
foreach($config as $k => $v) $config[$k] = config($k);
417
418             $
out = $ret = null;
419             maintenance_mode(
true);
420             $cmd =
"mysql -u{$config['dbUsername']} -p{$config['dbPassword']} -h{$config['dbServer']} {$config['dbDatabase']} < {$bfile}";
421             @exec($cmd, $
out, $ret);
422             maintenance_mode(
false);
423
424             
if($ret){ echo $cmd; return false; }
425
426             
return true;
427         }
428
429         
/**
430          * @brief Deletes a given backup file
431          *
432          * @
return Boolean indicating success or failure
433          */

434         
public function delete(){
435             $bfile = $
this->get_specified_backup_file();
436             
if(!$bfile) return false;
437
438             
return @unlink($bfile);
439         }
440
441         
protected function create_backup_folder(){
442             $bdir = $
this->curr_dir . '/backups';
443             
if(!is_dir($bdir))
444                 
if(!@mkdir($bdir)) return false;
445
446             
/* create .htaccess file preventing direct download of backup files */
447             
if(!is_file("{$bdir}/.htaccess"))
448                 @file_put_contents(
"{$bdir}/.htaccess",
449                     
"<FilesMatch \"\\.(sql)\$\">\n" .
450                     
" Order allow,deny\n" .
451                     
"</FilesMatch>"
452                 );
453
454             
/* create index.html empty file to prevent directory browsing */
455             
if(!is_file("{$bdir}/index.html")) @touch("{$bdir}/index.html");
456
457             
return true;
458         }
459     }



Hệ thống xếp lịch học tín chỉ cho sinh viên CNTT trên PHP & MySQL 112.134 lượt xem

Gõ tìm kiếm nhanh...